package MazeSolver;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.PriorityQueue;

public abstract class MazeModel {
	protected BufferedImage maze;
	protected PriorityQueue<Point> toVisit;
	protected Point[][] grid;
	protected Point start;
	protected Point end;
	protected ArrayList<Point> path;

	protected MazeView view;

	public MazeModel(BufferedImage maze, MazeView view) {
		this.maze = maze;
		this.view = view;

		grid = new Point[maze.getWidth()][maze.getHeight()];
		for(int i = 0; i < maze.getWidth(); i++) {
			for(int j = 0; j < maze.getHeight(); j++) {
				grid[i][j] = new Point(i,j,maze.getRGB(i, j));
			}
		}

		toVisit = new PriorityQueue<Point>();
		path = new ArrayList<Point>();
	}

	public void solve() {
		toVisit.add(start);

		Point current;
		boolean solved = false;

		int delay = 0;
		while(toVisit.size() != 0) {
			current = toVisit.remove();
			current.setVisited(true);
			view.updateOverlay(current.getX(), current.getY(), Color.green);

			if(current.equals(end)) {
				solved = true;
				break;
			}

			for(Point p : getNeighbors(current)) {
				if(!p.isVisited() && p.isPassable()) {
					int potentialCost = current.getCost() + calcCost(current,p);
					if(potentialCost < p.getCost()) {
						p.setCost(potentialCost);
						p.setPrevious(current);
						//p.setEstimatedCost(heuristic(p) + calcCost(current,p));
						//p.setEstimatedCost(p.getCost());

						if(toVisit.contains(p)) {
							toVisit.remove(p);
							toVisit.add(p);
						}
					}

					if(!toVisit.contains(p)) {
						p.setEstimatedCost(heuristic(p) + calcCost(current,p));
						toVisit.add(p);
						view.updateOverlay(p.getX(), p.getY(), Color.blue);
					}
				}
			}

			if(delay++ % 20 == 0) {
				printSolidPath(current);
				view.drawMaze();
			}

		}


		if(solved) {
			printSolidPath(end);
			view.drawMaze();
		}
		else
			System.out.println("No Solution");
	}

	public abstract int heuristic(Point p);

	private void clearPath() {
		for(Point p : path) {
			view.updateOverlay(p.getX(), p.getY(), Color.green);
		}
		path.clear();
	}

	public Point getStart() {
		return start;
	}

	public void setStart(Point start) {
		this.start = start;
	}

	public Point getEnd() {
		return end;
	}

	public void setEnd(Point end) {
		this.end = end;
	}

	private void printSolidPath(Point start) {
		clearPath();

		Point current = start;
		do{
			view.printSolution(current.getX(), current.getY(), Color.red);
			path.add(current);
			current = current.getPrevious();
		} while(current != null);
	}

	private int calcCost(Point current, Point p) {
		/*int cost = 0;
		int c1 = maze.getRGB(current.getX(), current.getY()) & 0xFFFFFF;
		int c2 =  maze.getRGB(p.getX(), p.getY()) & 0xFFFFFF;
		int diff = Math.abs(c1 - c2);

		if(Math.abs(current.getX() - p.getX()) + Math.abs(current.getY() - p.getY()) == 1) cost += 100;
		else cost += 140;

		//return Math.abs(current.getX() - p.getX()) + Math.abs(current.getY() - p.getY());
		if(maze.getRGB(current.getX(), current.getY()) > maze.getRGB(p.getX(), p.getY())) cost += 20 * diff;
		else if(maze.getRGB(current.getX(), current.getY()) > maze.getRGB(p.getX(), p.getY())) cost += 10 * diff;

		return cost;*/
		int cost = 0;
		if(Math.abs(current.getX() - p.getX()) + Math.abs(current.getY() - p.getY()) == 1) cost += 100;
		else cost += 140;
		return cost;
		
	}

	private ArrayList<Point> getNeighbors(Point p) {
		ArrayList<Point> neighbors = new ArrayList<Point>();
		for(int i = -1; i <= 1; i++) {
			for(int j = -1; j <= 1; j++) {
				if(!(i == 0 && j == 0)) {
					try {
						neighbors.add(grid[p.getX() + i][p.getY() + j]);
					} catch(ArrayIndexOutOfBoundsException e) {
						// out of bounds
					}
				}
			}
		}
		return neighbors;
	}

	public void setStart(int x, int y) {
		start = grid[x][y];
		view.updateStart();
	}

	public void setEnd(int x, int y) {
		if(end != null)
			end.setCost(Integer.MAX_VALUE);
		end = grid[x][y];
		end.setCost(0);
		view.updateEnd();
	}

	public void reset() {
		for(int i = 0; i < maze.getWidth(); i++) {
			for(int j = 0; j < maze.getHeight(); j++) {
				grid[i][j].setCost(Integer.MAX_VALUE);
				grid[i][j].setPrevious(null);
				grid[i][j].setVisited(false);
			}
		}
		start.setCost(0);

		toVisit = new PriorityQueue<Point>();
		path = new ArrayList<Point>();
		view.resetMaze();
	}

}
